package com.opennews.openplatform.myspringbootcore.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.Date;
import java.util.List;

@Component
public class TokenManager {
    /**
     * Generates signed token.
     *
     * @param claims:     The input claims to generate token.
     * @param signingKey: The key to sign token
     * @param expiration: How long the token expires in seconds.
     * @return String of generated and signed token. AKA jws.
     */
    public String generateToken(MyClaims claims, String signingKey, int expiration) {
        // Gets current instant for token issuedAt field.
        long epochSecond = Instant.now().getEpochSecond();
        claims.issuedAt = epochSecond;

        // Sets the expiresAt if needed.
        if (expiration > 0) {
            claims.expiresAt = epochSecond + expiration;
        }

        //        // Prepares map data for jws token generation.
        //        var jwsClaims = new HashMap<String, Object>();
        //
        //        // Standard fields. Package uses these fields for parseClaimsJws method.
        //        // It will determine whether this token is valid and not expired.
        //        jwsClaims.put("aud", claims.audience);
        //        jwsClaims.put("exp", claims.expiresAt);
        //        jwsClaims.put("iat", claims.issuedAt);
        //        jwsClaims.put("iss", claims.issuer);
        //        jwsClaims.put("nbf", claims.notBefore);
        //        jwsClaims.put("sub", claims.subject);
        //
        //        // Customized fields.
        //        jwsClaims.put("accountGroupId", claims.accountGroupId);
        //        jwsClaims.put("id", claims.id);
        //        jwsClaims.put("username", claims.username);
        //        jwsClaims.put("issuedAt", claims.issuedAt);
        //        jwsClaims.put("expiresAt", claims.expiresAt);
        //        jwsClaims.put("roles", claims.roles);

        // Prepares sign key for jws token generation.
        SecretKey key = Keys.hmacShaKeyFor(signingKey.getBytes());

        //        // Generates token with provides claims.
        //        var jws = Jwts.builder().setClaims(jwsClaims).signWith(key).compact();
        //
        //        // Returns generated an signed token.
        //        return jws;

        return Jwts.builder()
                // Standard claims
                .expiration(claims.expiresAt != null ? new Date(claims.expiresAt * 1000) : null) // Expiry in milliseconds
                .issuedAt(new Date(claims.issuedAt * 1000)) // Issued at in milliseconds
                .issuer(claims.issuer)
                .notBefore(claims.notBefore != null ? new Date(claims.notBefore * 1000) : null) // Not before in milliseconds
                .subject(claims.subject)
                // Custom claims
                .claim("accountGroupId", claims.accountGroupId)
                .claim("id", claims.id)
                .claim("username", claims.username)
                .claim("deviceId", claims.deviceId)
                .claim("roles", claims.roles)
                .signWith(key)
                .compact();
    }

    /**
     * Validates jws and returns BaseClaims instance.
     *
     * @param jws:        Generated and signed token.
     * @param signingKey: The key to unsign token.
     */
    public ParsedToken validateToken(String jws, String signingKey) {
        ParsedToken parsedToken = new ParsedToken();
        SecretKey key = Keys.hmacShaKeyFor(signingKey.getBytes());

        try {
            Claims jwsClaims = Jwts.parser().verifyWith(key).build().parseSignedClaims(jws).getPayload();
            parsedToken.valid = true;
            parsedToken.claims = new MyClaims();
            parsedToken.claims.accountGroupId = jwsClaims.get("accountGroupId").toString();
            parsedToken.claims.id = jwsClaims.get("id").toString();
            parsedToken.claims.username = jwsClaims.get("username").toString();
            parsedToken.claims.deviceId = jwsClaims.get("deviceId").toString();
            parsedToken.claims.issuedAt = (Long) jwsClaims.get("iat");
            parsedToken.claims.expiresAt = (Long) jwsClaims.get("exp");
            parsedToken.claims.roles = (List<String>) jwsClaims.get("roles");
        } catch (Exception ex) {
            parsedToken.valid = false;

            if (ex instanceof ExpiredJwtException) {
                parsedToken.expired = true;
            }
        }

        return parsedToken;
    }
}
